using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;

using Xfinity.Engine;
using Xfinity.Engine.Plugins;
using Xfinity.Settings;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using Xfinity.Dice;

namespace Xfinity.Console
{
    /// <summary>
    /// Some default basic commands.
    /// </summary>
    public static class DefaultCommands
    {
        #region Echo
        /// <summary>
        /// Echo.
        /// </summary>
        /// <remarks>
        /// Echoes the input.
        /// </remarks>
        /// <example>
        /// echo I'M BEING ECHOED.
        /// </example>
        private const string EchoHelp = "echo I'M BEING ECHOED.";
        [BindCommand("Echo", Description = "Echoes the input.", Help = EchoHelp)]
        public static void Echo(string[] commands)
        {
            StandardConsole.WriteLine(GetArguments(commands));
        }
        #endregion
        #region Cat
        /// <summary>
        /// Cat.
        /// </summary>
        /// <remarks>
        /// Concatenates to the following Command.
        /// </remarks>
        [BindCommand("Cat", Description = "Concatenates to the following Command.")]
        public static void Cat(string[] commands)
        {
            int currentLastIndex = StandardConsole.Transcript.Count-1;
            StandardConsole.WriteLine("GOtta implement this still");
        }
        #endregion
        #region Roll
        /// <summary>
        /// Roll.
        /// </summary>
        /// <remarks>
        /// Rolls dice.
        /// </remarks>
        /// <example>
        /// Roll 4d20 + 3
        /// </example>
        private const string RollHelp = "Roll 4d20 + 3";
        [BindCommand("Roll", Description = "Rolls dice.", Help = RollHelp)]
        public static void Roll(string[] commands)
        {
            string die = DefaultCommands.GetArguments(commands);
            // THE REGEX FROM HELL D:
            Regex regex = new Regex(@"^(?<number>\d+)?[dD](?<sides>\d+)(?<modifier>[+-]\d+)?$");
            Match match = regex.Match(die);
            if (!match.Success)
            {
                StandardConsole.WriteLine("Format: Roll [<number of rolls>][d/D]<number of sides> [+/- <modifier>]. If <number of rolls> is omitted, default is 1.");
                return;
            }
            string extractedNumber = match.Groups["number"].Value;
            string extractedSides = match.Groups["sides"].Value;
            string extractedModifier = match.Groups["modifier"].Value;
            int numberOfTimesToRoll = 0;
            byte sides = 0;
            int modifier= 0;
            if (!String.IsNullOrEmpty(extractedNumber))
            {
                numberOfTimesToRoll = Int32.Parse(extractedNumber);
            }
            if (numberOfTimesToRoll == 0)
            {
                numberOfTimesToRoll = 1;
            }
            if (!String.IsNullOrEmpty(extractedSides))
            {
                sides = Byte.Parse(extractedSides);
            }

            if (!String.IsNullOrEmpty(extractedModifier))
            {
                modifier = Int32.Parse(extractedModifier);
            }
            Die dice = new Die(sides);
            int result = dice.Roll(numberOfTimesToRoll, modifier);
            string output = result.ToString();
            StandardConsole.WriteLine(output);
        }
        #endregion
        /// <summary>
        /// ListCommands.
        /// </summary>
        [BindCommand("ListCommands")]
        public static void ListCommands(string[] commands)
        {
            List<string> list = new List<string>(CommandParser.Commands.Count);
            foreach (Command command in CommandParser.Commands.Values)
            {
                string line = CommandParser.DescribeCommand(command);
                list.Add(line);
            }
            StandardConsole.OutputSortedList(list);
        }

        /// <summary>
        /// FindCommand.
        /// </summary>
        [BindCommand("FindCommand", Description = "Searches for commands given a regular expression.")]
        public static void FindCommand(string[] commands)
        {
            string searchee = CommandParser.ReconstructArguments(commands);
            List<Command> cmds;
            List<string> matches = CommandParser.GetMatchingCommands(searchee, true, out cmds);
            StandardConsole.OutputSortedList(matches);
        }

        /// <summary>
        /// ListKeys.
        /// </summary>
        [BindCommand("ListKeys")]
        public static void ListKeys(string[] commands)
        {
            List<string> mappings = new List<string>();
            foreach (InputHandler.KeyMapping mapping in InputHandler.BoundCommands)
            {
                mappings.Add(mapping.Key.ToString() + ": " + mapping.Command);
            }
            StandardConsole.OutputSortedList(mappings);
        }

        /// <summary>
        /// Man pages.
        /// </summary>
        /// <remarks>
        /// Lists help about a Command.
        /// </remarks>
        /// <example>
        /// man &ltcommand&gt
        /// </example>
        private const string ManHelp = "man <Command>";
        [BindCommand("Man", Description = "Lists help about a Command.", Help = ManHelp)]
        public static void Man(string[] commands)
        {
            try
            {
                if (String.IsNullOrEmpty(commands[1]))
                {
                    StandardConsole.Fail(ManHelp);
                    return;
                }
                string command = commands[1];
                if (string.IsNullOrEmpty(command))
                {
                    StandardConsole.WriteLine("No help available.");
                    return;
                }
                StandardConsole.WriteLine(CommandParser.Commands[command].Help);
            }
            catch (IndexOutOfRangeException)
            {
                StandardConsole.Fail(ManHelp);
            }
            catch (KeyNotFoundException)
            {
                StandardConsole.Fail(commands[1] + ": Command doesn't exist.");
            }
        }

        /// <summary>
        /// Sets the specified property.
        /// </summary>
        /// <param name="commands">The value.</param>
        [BindCommand("set")]
        public static void Set(string[] commands)
        {
            try
            {
                string variableName = commands[1];
                Type engine = typeof(EngineSettings);
                bool beenSet = false;
                PropertyInfo[] prop = engine.GetProperties();
                foreach (PropertyInfo info in prop)
                {
                    if (info.Name == variableName)
                    {
                        info.SetValue(null, GetArguments(commands), null);
                        beenSet = true;
                    }
                }
                if (!beenSet)
                {
                    StandardConsole.WriteLine("Command usage: set <property> <value>. Note: valid arguments must be supplied.");
                }
            }
            catch (TargetInvocationException)
            {
                StandardConsole.WriteLine("Command usage: set <property> <value>. Note: valid arguments must be supplied.");
            }
        }

        [BindCommand("Exit")]
        public static void Exit(string[] commands)
        {
            GameManager.Game.Exit();
        }

        [BindCommand("LookAt")]
        public static void LookAt(string[] commands)
        {
            try
            {
                float x = float.Parse(commands[1]);
                float y = float.Parse(commands[2]);
                float z = float.Parse(commands[3]);
                Vector3 v = new Vector3(x, y, z);
                System.Diagnostics.Debug.WriteLine(v, "Look at");
                GraphicsHelper.ActiveCamera.LookAt(v);
            }
            catch (FormatException)
            {
                StandardConsole.WriteLine("Command usage: lookat {x} {y} {z}");
            }
            catch (IndexOutOfRangeException)
            {
                StandardConsole.WriteLine("Command usage: lookat {x} {y} {z}");
            }

        }

        [BindCommand("MoveTo")]
        public static void MoveTo(string[] commands)
        {
            try
            {
                float x = float.Parse(commands[1]);
                float y = float.Parse(commands[2]);
                float z = float.Parse(commands[3]);
                Vector3 v = new Vector3(x, y, z);
                System.Diagnostics.Debug.WriteLine(v, "Move to");
                GraphicsHelper.ActiveCamera.Position = v;
            }
            catch (FormatException)
            {
                StandardConsole.WriteLine("Command usage: moveto {x} {y} {z}");
            }
            catch (IndexOutOfRangeException)
            {
                StandardConsole.WriteLine("Command usage: moveto {x} {y} {z}");
            }

        }

        /// <summary>
        /// Binds the specified commands to a key.
        /// </summary>
        /// <param name="commands">The commands.</param>
        [BindCommand("bind")]
        public static void Bind(string[] commands)
        {
            if (commands.Length < 3)
            {
                StandardConsole.WriteLine("Command usage: bind <key> <Command>");
                return;
            }
            Keys key = (Keys)Enum.Parse(typeof(Keys),
                                 commands[1],
                                 true);

            string command = GetArguments(commands);

            InputHandler.BindToCommand(key, command);

            System.Diagnostics.Trace.WriteLine("Bound " + key.ToString() + " to " + command);
        }

        /// <summary>
        /// Gets the arguments in string form.
        /// </summary>
        /// <param name="commands">The commands.</param>
        /// <returns></returns>
        private static string GetArguments(string[] commands)
        {
            string command = String.Join(" ", commands, 1, commands.Length - 1);
            return command;
        }
        
        /// <summary>
        /// Toggles the game pause state.
        /// </summary>
        /// <param name="commands">The commands.</param>
        [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "commands", Justification = "This is just the way the delegate is.")]
        [BindCommand("pause")]
        public static void Pause(string[] commands)
        {
            GameManager.Paused = !GameManager.Paused;
        }

        /// <summary>
        /// Changes the player name.
        /// </summary>
        /// <param name="commands">The name.</param>
        [BindCommand("name")]
        public static void ChangeName(string[] commands)
        {
            try
            {
                Xfinity.Settings.EngineSettings.PlayerName = string.Join(" ", commands, 1, commands.Length - 1);
                Xfinity.Settings.EngineSettings.Save();
            }
            catch (ArgumentOutOfRangeException)
            {
                StandardConsole.WriteLine("Command usage: name <player name>.");
            }
        }
    }
}